const YEAR = 'year';
const MONTH = 'month';
const DAY = 'day';
const HOUR = 'hour';
const MINUTE = 'minute';
const SECOND = 'second';
const MILLISECOND = 'millisecond';

const month_names = {
	en: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
	es: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
	ru: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
	ptBr: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
	fr: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
	tr: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'],
	zh: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
};

export default {
	parse(date, date_separator = '-', time_separator = /[.:]/) {
		if (date instanceof Date) {
			return date;
		}
		if (typeof date === 'string') {
			let date_parts, time_parts;
			const parts = date.split(' ');

			date_parts = parts[0].split(date_separator).map((val) => parseInt(val, 10));
			time_parts = parts[1] && parts[1].split(time_separator);

			// month is 0 indexed
			date_parts[1] = date_parts[1] - 1;

			let vals = date_parts;

			if (time_parts && time_parts.length) {
				if (time_parts.length == 4) {
					time_parts[3] = '0.' + time_parts[3];
					time_parts[3] = parseFloat(time_parts[3]) * 1000;
				}
				vals = vals.concat(time_parts);
			}

			return new Date(...vals);
		}
	},

	to_string(date, with_time = false) {
		if (!(date instanceof Date)) {
			throw new TypeError('Invalid argument type');
		}
		const vals = this.get_date_values(date).map((val, i) => {
			if (i === 1) {
				// add 1 for month
				val = val + 1;
			}

			if (i === 6) {
				return padStart(val + '', 3, '0');
			}

			return padStart(val + '', 2, '0');
		});
		const date_string = `${vals[0]}-${vals[1]}-${vals[2]}`;
		const time_string = `${vals[3]}:${vals[4]}:${vals[5]}.${vals[6]}`;

		return date_string + (with_time ? ' ' + time_string : '');
	},

	format(date, format_string = 'YYYY-MM-DD HH:mm:ss.SSS', lang = 'en') {
		const values = this.get_date_values(date).map((d) => padStart(d, 2, 0));
		const format_map = {
			YYYY: values[0],
			MM: padStart(+values[1] + 1, 2, 0),
			DD: values[2],
			HH: values[3],
			mm: values[4],
			ss: values[5],
			SSS: values[6],
			D: values[2],
			MMMM: month_names[lang][+values[1]],
			MMM: month_names[lang][+values[1]]
		};

		let str = format_string;
		const formatted_values = [];

		Object.keys(format_map)
			.sort((a, b) => b.length - a.length) // big string first
			.forEach((key) => {
				if (str.includes(key)) {
					str = str.replace(key, `$${formatted_values.length}`);
					formatted_values.push(format_map[key]);
				}
			});

		formatted_values.forEach((value, i) => {
			str = str.replace(`$${i}`, value);
		});

		return str;
	},

	diff(date_a, date_b, scale = DAY) {
		let milliseconds, seconds, hours, minutes, days, months, years;

		milliseconds = date_a - date_b;
		seconds = milliseconds / 1000;
		minutes = seconds / 60;
		hours = minutes / 60;
		days = hours / 24;
		months = days / 30;
		years = months / 12;

		if (!scale.endsWith('s')) {
			scale += 's';
		}

		return Math.floor(
			{
				milliseconds,
				seconds,
				minutes,
				hours,
				days,
				months,
				years
			}[scale]
		);
	},

	today() {
		const vals = this.get_date_values(new Date()).slice(0, 3);
		return new Date(...vals);
	},

	now() {
		return new Date();
	},

	add(date, qty, scale) {
		qty = parseInt(qty, 10);
		const vals = [
			date.getFullYear() + (scale === YEAR ? qty : 0),
			date.getMonth() + (scale === MONTH ? qty : 0),
			date.getDate() + (scale === DAY ? qty : 0),
			date.getHours() + (scale === HOUR ? qty : 0),
			date.getMinutes() + (scale === MINUTE ? qty : 0),
			date.getSeconds() + (scale === SECOND ? qty : 0),
			date.getMilliseconds() + (scale === MILLISECOND ? qty : 0)
		];
		return new Date(...vals);
	},

	start_of(date, scale) {
		const scores = {
			[YEAR]: 6,
			[MONTH]: 5,
			[DAY]: 4,
			[HOUR]: 3,
			[MINUTE]: 2,
			[SECOND]: 1,
			[MILLISECOND]: 0
		};

		function should_reset(_scale) {
			const max_score = scores[scale];
			return scores[_scale] <= max_score;
		}

		const vals = [
			date.getFullYear(),
			should_reset(YEAR) ? 0 : date.getMonth(),
			should_reset(MONTH) ? 1 : date.getDate(),
			should_reset(DAY) ? 0 : date.getHours(),
			should_reset(HOUR) ? 0 : date.getMinutes(),
			should_reset(MINUTE) ? 0 : date.getSeconds(),
			should_reset(SECOND) ? 0 : date.getMilliseconds()
		];

		return new Date(...vals);
	},

	clone(date) {
		return new Date(...this.get_date_values(date));
	},

	get_date_values(date) {
		return [date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()];
	},

	get_days_in_month(date) {
		const no_of_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

		const month = date.getMonth();

		if (month !== 1) {
			return no_of_days[month];
		}

		// Feb
		const year = date.getFullYear();
		if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
			return 29;
		}
		return 28;
	}
};

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
function padStart(str, targetLength, padString) {
	str = str + '';
	targetLength = targetLength >> 0;
	padString = String(typeof padString !== 'undefined' ? padString : ' ');
	if (str.length > targetLength) {
		return String(str);
	} else {
		targetLength = targetLength - str.length;
		if (targetLength > padString.length) {
			padString += padString.repeat(targetLength / padString.length);
		}
		return padString.slice(0, targetLength) + String(str);
	}
}
